#include <iostream>
#include <sstream>
#include <ruby.h>
#include "m3g/m3g.hpp"
#include "ruby-m3g.hpp"
using namespace m3g;
using namespace std;

namespace {
    struct IndexBufferAccessor {
        Mesh* mesh;
    };
    struct AppearanceAccessor {
        Mesh* mesh;
    };
    VALUE rb_cMesh_IndexBufferAccessor;
    VALUE rb_cMesh_AppearanceAccessor;
}

void ruby_Mesh_mark (Mesh* ptr)
{
    vector<Object3D*> objs;
    extract_all_references (ptr, objs);
    for (int i = 0; i < (int)objs.size(); i++) {
        VALUE val_obj = (VALUE)objs[i]->getExportedEntity();
        if (val_obj)
            rb_gc_mark (val_obj);
    }
}

void ruby_Mesh_free (Mesh* ptr)
{
    if (valid_ptr((unsigned char*)ptr, sizeof(*ptr))) {
        __TRY__;
        ptr->~Mesh ();
        __CATCH__;
    }
    ruby_xfree (ptr);
}

VALUE ruby_Mesh_allocate (VALUE self)
{
    void* p = ruby_xmalloc (sizeof(Mesh));
    return Data_Wrap_Struct (self, ruby_Mesh_mark, ruby_Mesh_free, p);
}

VALUE ruby_Mesh_initialize (VALUE self, VALUE val_vertices, VALUE val_submeshes, VALUE val_appearances)
{
    if (!rb_obj_is_kind_of (val_vertices, rb_cVertexBuffer)) {
        rb_raise (rb_eArgError, "wrong argument type (expected M3G::VertexBuffer)" );
    }
    if (!((TYPE(val_submeshes)==T_ARRAY && TYPE(val_appearances)==T_ARRAY) ||
          (TYPE(val_submeshes)==T_DATA  && TYPE(val_appearances)==T_DATA))) {
        rb_raise (rb_eArgError, "wrong argument type (expected IndexBuffer or Array of IndexBuffer and Appearance or Array of Appearance)");
    }

    Mesh* p;
    Data_Get_Struct (self, Mesh, p);
    VertexBuffer* vertices;
    Data_Get_Struct (val_vertices, VertexBuffer, vertices);

    switch (TYPE(val_submeshes)) {
    case T_ARRAY: {
        int           num_submeshes   = RARRAY_LEN(val_submeshes);
        int           num_appearances = RARRAY_LEN(val_appearances);
        IndexBuffer** submeshes   = (IndexBuffer**)ruby_xmalloc (sizeof(IndexBuffer*)*num_submeshes);
        Appearance**  appearances = (Appearance**) ruby_xmalloc (sizeof(Appearance* )*num_appearances);
        for (int i = 0; i < num_submeshes; i++) {
            VALUE val_submesh = rb_ary_entry(val_submeshes, i);
            if (!rb_obj_is_kind_of (val_submesh, rb_cIndexBuffer)) {
                rb_raise (rb_eArgError, "wrong argument type (expected M3G::IndexBuffer)" );
            }
            Data_Get_Struct (val_submesh, IndexBuffer, submeshes[i]);
        }
        for (int i = 0; i < num_appearances; i++) {
            VALUE val_appearance = rb_ary_entry(val_appearances, i);
            if (!rb_obj_is_kind_of (val_appearance, rb_cAppearance)) {
                rb_raise (rb_eArgError, "wrong argument type (expected M3G::Appearance)" );
            }
            Data_Get_Struct (val_appearance, Appearance, appearances[i]);
        }
        __TRY__;
        new (p) Mesh (vertices, num_submeshes, submeshes, appearances);
        __CATCH__;
        ruby_xfree (submeshes  );
        ruby_xfree (appearances);
        break;
    }
    case T_DATA: {
        if (!rb_obj_is_kind_of (val_submeshes, rb_cIndexBuffer)) {
            rb_raise (rb_eArgError, "wrong argument type (expected M3G::IndexBuffer)" );
        }
        if (!rb_obj_is_kind_of (val_appearances, rb_cAppearance)) {
            rb_raise (rb_eArgError, "wrong argument type (expected M3G::Appearance)" );
        }
        IndexBuffer* submesh;
        Appearance*  appearance;
        Data_Get_Struct (val_submeshes  , IndexBuffer, submesh);
        Data_Get_Struct (val_appearances, Appearance , appearance);
        __TRY__;
        new (p) Mesh (vertices, submesh, appearance);
        __CATCH__;
        break;
    }
    default: {
        rb_raise (rb_eArgError, "wrong argument type (expected IndexBuffer and Appearance (or Array))");
    }
    }

    p->setExportedEntity ((void*)self);
    return self;
}

VALUE ruby_Mesh_get_appearance (VALUE self)
{
    Mesh* p;
    Data_Get_Struct (self, Mesh, p);
    AppearanceAccessor* accessor;
    VALUE           val_accessor = Data_Make_Struct (rb_cMesh_AppearanceAccessor, AppearanceAccessor, 0, -1, accessor);
    accessor->mesh = p;
    return val_accessor;
}

VALUE ruby_Mesh_get_index_buffer (VALUE self)
{
    Mesh* p;
    Data_Get_Struct (self, Mesh, p);
    IndexBufferAccessor* accessor;
    VALUE            val_accessor = Data_Make_Struct (rb_cMesh_IndexBufferAccessor, IndexBufferAccessor, 0, -1, accessor);
    accessor->mesh = p;
    return val_accessor;
}

VALUE ruby_Mesh_get_submesh_count (VALUE self)
{
    Mesh* p;
    Data_Get_Struct (self, Mesh, p);
    int count;
    __TRY__;
    count = p->getSubmeshCount ();
    __CATCH__;
    return INT2NUM (count);
}

VALUE ruby_Mesh_get_vertex_buffer (VALUE self)
{
    Mesh* p;
    Data_Get_Struct (self, Mesh, p);
    VertexBuffer* vertices;
    __TRY__;
    vertices = p->getVertexBuffer();
    __CATCH__;
    return vertices ? (VALUE)vertices->getExportedEntity () : Qnil;
}

VALUE ruby_Mesh_print (VALUE self)
{
    Mesh* p;
    Data_Get_Struct (self, Mesh, p);
    ostringstream out;
    __TRY__;
    p->print (out);
    __CATCH__;
    return rb_str_new2 (out.str().c_str());
}


/**
 * Mesh_IndexBufferAccessor
 */

VALUE ruby_Mesh_IndexBufferAccessor_get_index_buffer (VALUE self, VALUE val_index)
{
    IndexBufferAccessor* p;
    Data_Get_Struct (self, IndexBufferAccessor, p);
    int index = NUM2INT (val_index);
    IndexBuffer* ibuf;
    __TRY__;
    ibuf = p->mesh->getIndexBuffer (index);
    __CATCH__;
    return ibuf ? (VALUE)ibuf->getExportedEntity() : Qnil;
}

/**
 * Mesh_AppearanceAccessor
 */

VALUE ruby_Mesh_AppearanceAccessor_get_appearance (VALUE self, VALUE val_index)
{
    AppearanceAccessor* p;
    Data_Get_Struct (self, AppearanceAccessor, p);
    int index = NUM2INT (val_index);
    Appearance* app;
    __TRY__;
    app = p->mesh->getAppearance (index);
    __CATCH__;
    return app ? (VALUE)app->getExportedEntity() : Qnil;
}

VALUE ruby_Mesh_AppearanceAccessor_set_appearance (VALUE self, VALUE val_index, VALUE val_appearance)
{
    if (val_appearance != Qnil && !rb_obj_is_kind_of (val_appearance, rb_cAppearance)) {
        rb_raise (rb_eArgError, "wrong argument type (expected M3G::Appearance)");
    }
    AppearanceAccessor* p;
    Data_Get_Struct (self, AppearanceAccessor, p);
    int index = NUM2INT (val_index);
    Appearance* app = NULL;
    if (val_appearance != Qnil) {
        Data_Get_Struct (val_appearance, Appearance, app);
    }
    __TRY__;
    p->mesh->setAppearance (index, app);
    __CATCH__;
    return Qnil;
}



/**
 *
 */
void register_Mesh ()
{
    // Mesh
    rb_cMesh                = rb_define_class_under (rb_mM3G, "Mesh",                rb_cNode);

    rb_define_alloc_func     (rb_cMesh, ruby_Mesh_allocate);
    rb_define_private_method (rb_cMesh, "initialize", (VALUE(*)(...))ruby_Mesh_initialize, 3);

    rb_define_method (rb_cMesh, "appearance"   ,  (VALUE(*)(...))ruby_Mesh_get_appearance   , 0);
    rb_define_method (rb_cMesh, "index_buffer" ,  (VALUE(*)(...))ruby_Mesh_get_index_buffer , 0);
    rb_define_method (rb_cMesh, "submesh_count",  (VALUE(*)(...))ruby_Mesh_get_submesh_count, 0);
    rb_define_method (rb_cMesh, "vertex_buffer",  (VALUE(*)(...))ruby_Mesh_get_vertex_buffer, 0);
    rb_define_method (rb_cMesh, "to_s"         ,  (VALUE(*)(...))ruby_Mesh_print            , 0);

    // Mesh_IndexBufferAccessor
    rb_cMesh_IndexBufferAccessor  = rb_define_class_under (rb_cMesh, "IndexBufferAccessor", rb_cObject);

    rb_define_method (rb_cMesh_IndexBufferAccessor, "[]",    (VALUE(*)(...))ruby_Mesh_IndexBufferAccessor_get_index_buffer, 1);

    // Mesh_AppearanceAccessor;
    rb_cMesh_AppearanceAccessor  = rb_define_class_under (rb_cMesh, "ApearanceAccessor", rb_cObject);

    rb_define_method (rb_cMesh_AppearanceAccessor, "[]",     (VALUE(*)(...))ruby_Mesh_AppearanceAccessor_get_appearance, 1);
    rb_define_method (rb_cMesh_AppearanceAccessor, "[]=",    (VALUE(*)(...))ruby_Mesh_AppearanceAccessor_set_appearance, 2);
}
